# Copyright (c) 2020 cgsdfc
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
"""
Create a preprocessed version of Python-ast.h for the C parser to consume.
"""

import itertools
from distutils.sysconfig import get_python_inc
from pathlib import Path
import re

PYTHON_AST_H = 'Python-ast.h'


def GetPythonAstHeaderPath(include_dir: Path):
    p = include_dir / PYTHON_AST_H
    assert p.exists()
    return p


class Preprocessor:
    "Holds the preprocess steps and logic."

    # These lines need to be removed.
    PP_DIRECTIVE = r'#[^\n]+'
    SINGLE_LINE_COMMENT = r'//[^\n]*'
    MULTI_LINE_COMMENT = r'/\*.*?\*/'
    EXTERN_C_BEGIN = r'extern "C"'
    EXTERN_C_END = r'^\}$'

    REMOVE_STUFF_RE = re.compile('|'.join(
        (PP_DIRECTIVE, SINGLE_LINE_COMMENT, MULTI_LINE_COMMENT, EXTERN_C_BEGIN,
         EXTERN_C_END)))

    # These types needs to be `typedef`ed.
    TYPEDEF_TYPES = (
        'asdl_seq',
        'asdl_int_seq',
        'object',
        'string',
        'identifier',
        'constant',
        'singleton',
        'bytes',
        'PyArena',
        'PyObject',
    )

    @classmethod
    def Typedef(cls, typename):
        return 'typedef void* {};\n'.format(typename)

    @classmethod
    def CreateTypedefs(cls):
        return list(map(cls.Typedef, cls.TYPEDEF_TYPES))

    def __init__(self, filename: Path):
        self._lines = filename.read_text().splitlines(keepends=True)

    def AddTypedefs(self):
        self._lines = self.CreateTypedefs() + self._lines

    def RemoveCommentAndDirectives(self):
        "Remove preprocessor directives."
        self._lines = list(
            itertools.filterfalse(self.REMOVE_STUFF_RE.match, self._lines))

    def Preprocess(self):
        self.RemoveCommentAndDirectives()
        self.AddTypedefs()

    def Dump(self, stream):
        stream.writelines(self._lines)


if __name__ == "__main__":
    from argparse import ArgumentParser
    p = ArgumentParser()
    p.add_argument('-i', dest='include_dir', help='Python include dir')
    p.add_argument('-o', dest='output', help='output path')
    args = p.parse_args()
    output = Path(args.output)

    filename = GetPythonAstHeaderPath(Path(args.include_dir))
    pp = Preprocessor(filename)
    pp.Preprocess()
    pp.Dump(output.open('w'))
